package com.zx.idc.backend.gui.api.management.scheduler.base;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.toolkit.IdWorker;
import com.zx.idc.backend.gui.api.commom.entity.UploadModel;
import com.zx.idc.backend.gui.api.commom.template.NIdcFtpTemplate;
import com.zx.idc.common.attributes.FTPReportDataTypeEnum;
import com.zx.idc.common.attributes.NetCommunicationFileTypeEnum;
import com.zx.idc.common.attributes.SchemaFileNameEnum;
import com.zx.idc.common.configuration.AppProtocolConfiguration;
import com.zx.idc.common.configuration.ContentTypeRepo;
import com.zx.idc.common.constants.CommonConstant;
import com.zx.idc.common.lang.ListUtil;
import com.zx.idc.common.lang.PathUtil;
import com.zx.idc.common.ref.ClassGenericUtil;
import com.zx.idc.common.spring.SpringConfigUtil;
import com.zx.idc.common.util.FileUtils;
import com.zx.idc.common.util.LocalDateTimeFormatUtil;
import com.zx.idc.ds.base.house.entity.BaseHouseInfo;
import com.zx.idc.ds.base.house.service.IBaseHouseInfoService;
import com.zx.idc.ds.base.oper.service.IBaseOperCompanyService;
import com.zx.idc.ds.common.constant.ReportStatusConstant;
import com.zx.idc.ds.common.service.BaseService;
import com.zx.idc.ds.ins.base.BaseFilterAndMonitorEntity;
import com.zx.idc.ds.sys.service.ISysConfigurationService;
import com.zx.idc.xml.entity.common.filtermonitor.attachment.Attachment;
import com.zx.idc.xml.entity.common.filtermonitor.log.Log;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections4.CollectionUtils;
import org.assertj.core.util.Lists;
import org.joor.Reflect;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 过滤 监测 指令 上报操作的抽象
 *
 * @param <T> 上报传输的实体类
 */
public abstract class BaseFilterAndMonitorJob<S extends BaseService<E>, E extends BaseFilterAndMonitorEntity, T> implements Job {
    protected final Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private S service;

    /**
     * 编码
     */
    private static final String CHARSET_UTF8 = "UTF-8";

    /**
     * 是否匹配关键字 1：是，0：否
     */
    private static final String MATCH_KEYWORD_YES = "1";

    /**
     * 是否匹配关键字 1：是，0：否
     */
    private static final String MATCH_KEYWORD_NO = "2";

    /**
     * 文件后缀
     */
    private static final String FILE_SUFFIX = ".lock";

    /**
     * 日志路径
     */
    private final String logFileDirPath;

    /**
     * schema文件名
     */
    private final SchemaFileNameEnum schemaFileName;

    /**
     * 上传路径
     */
    private final FTPReportDataTypeEnum uploadPathKey;

    /**
     * 通讯记录的文件类型
     */
    private final NetCommunicationFileTypeEnum fileTypeEnum;

    /**
     * 任务名称
     */
    private final String jobName;

    @Autowired
    private NIdcFtpTemplate nIdcFtpTemplate;

    @Autowired
    private ContentTypeRepo contentTypeRepo;

    @Autowired
    private IBaseHouseInfoService iBaseHouseInfoService;

    @Autowired
    private IBaseOperCompanyService iBaseOperCompanyService;

    @Autowired
    private ISysConfigurationService iSysConfigurationService;

    public BaseFilterAndMonitorJob(String logFileDirPath, SchemaFileNameEnum schemaFileName, FTPReportDataTypeEnum uploadPathKey, NetCommunicationFileTypeEnum fileTypeEnum, String jobName
    ) {
        this.logFileDirPath = logFileDirPath;
        this.schemaFileName = schemaFileName;
        this.uploadPathKey = uploadPathKey;
        this.fileTypeEnum = fileTypeEnum;
        this.jobName = jobName;
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            log.info("{}开始执行", jobName);

            String idcId = iBaseOperCompanyService.getIdcId();
            List<BaseHouseInfo> baseHouseInfos = iBaseHouseInfoService.selectList(new EntityWrapper<>(new BaseHouseInfo().setIdcId(idcId)).setSqlSelect("id"));
            if (baseHouseInfos == null || baseHouseInfos.isEmpty()) {
                log.info("经营许可证号:{}下不存在机房,{}执行结束", idcId, jobName);
                return;
            }
            String rootDir = FileUtils.checkDirPathEndSeparator(SpringConfigUtil.get(logFileDirPath));
            //获取当前类的类型参数列表 0：service,1:entity,2:uploadEntity
            List<Class<?>> baseGenericClasses = ClassGenericUtil.getSuperClassTypeParams(getClass());
            Class<?> entityClazz = baseGenericClasses.get(1);
            E entity = Reflect.on(entityClazz).create().get();

            for (BaseHouseInfo house : baseHouseInfos) {
                //过滤文件
                String houseId = house.getId() + "";

                String scanDirPath = PathUtil.join(rootDir, houseId);

                File[] files = new File(scanDirPath).listFiles((file, fileName) -> !fileName.toLowerCase().endsWith(FILE_SUFFIX));
                if (files == null || files.length == 0) {
                    log.debug(jobName + "not files,continue!");
                    continue;
                }

                //读出文件进行上报 删除文件
                for (File file : files) {
                    log.debug(jobName + "report file start!");
                    Path filePath = file.toPath();

                    //读出数据
                    List<String> strings = Files.readAllLines(filePath, StandardCharsets.UTF_8);
                    if (ListUtil.isEmpty(strings)) {
                        log.debug(jobName + "filePath:" + filePath + "is empty,continue!");
                        continue;
                    }
                    //文件内容转换为上报内容
                    log.debug(jobName + "Convert file content to report content start!");
                    List<Log> baseLogs = dataConversion(strings, house.getId());
                    log.debug(jobName + "Convert file content to report content done!");
                    if (CollectionUtils.isEmpty(baseLogs)) {
                        log.error("没有{}日志，跳过本次上报", jobName);
                        Files.deleteIfExists(filePath);
                        return;
                    }
                    log.debug(jobName + "upload file start");
                    T t = reportData(baseLogs, idcId);
                    updateReportStatus(baseLogs, entity, ReportStatusConstant.REPORTING);
                    //上报
                    try {
                        nIdcFtpTemplate.uploadXMLFile(
                            new UploadModel<>(
                                t,
                                schemaFileName,
                                uploadPathKey,
                                fileTypeEnum
                            )
                        );
                        Files.deleteIfExists(filePath);
                    } catch (Exception e) {
                        //回滚
                        log.error(jobName + "upload file is failed!", e);
                        updateReportStatus(baseLogs, entity, ReportStatusConstant.NOT_REPORTED);
                    }
                    log.debug(jobName + "upload file done");
                }
            }
            log.info("{}执行成功", jobName);
        } catch (Exception e) {
            log.error("{} read log file is fail", jobName, e);
        }

    }


    private void updateReportStatus(List<Log> baseLogs, E entity, Integer reportStatus) {
        Set<E> data = baseLogs.stream().map(obj -> {
            entity.setId(String.valueOf(obj.getCommandId()));
            entity.setReportStatus(reportStatus);
            return entity;
        }).collect(Collectors.toSet());
        service.updateBatchById(new LinkedList<>(data));
    }

    /**
     * 定义上报实体
     *
     * @param logs
     * @param idcId
     * @return
     */
    protected abstract T reportData(List<Log> logs, String idcId);

    /**
     * 数据转换 数据对应表
     *   0      |  1  |   2  |   3   |    4   |5  |     6     |     7             |     8    |      9    |
     * 指令ID    |源IP |目的IP |源端口  |目的端口 |URL|访问内容类型 |应用协议类型         |采集时间   |是否匹配关键词|
     * commandId|srcIp|destIp|srcPort|destPort|url|ContentType|applicationProtocol|gatherTime|matchKeyword|
     * @param strings
     * @return
     */
    private List<Log> dataConversion(List<String> strings, Long houseId) {
        return strings.stream()
            .filter(this::filter)
            .map((text) -> {
            String[] split = text.split(CommonConstant.LOG_FIELD_SEPARATOR);
            long commandId = Long.parseLong(split[0]);
            String srcIp = split[1];
            String destIp = split[2];
            long srcPort = Long.parseLong(split[3]);
            long destPort = Long.parseLong(split[4]);
            String url = split[5];
            LocalDateTime gatherTime = LocalDateTimeFormatUtil.parseLocalDateTime(split[8]);

            Log log = new Log()
                .setLogId(IdWorker.getId())
                .setCommandId(commandId)
                .setHouseId(houseId)
                .setSrcIp(srcIp)
                .setDestIp(destIp)
                .setSrcPort(srcPort)
                .setDestPort(destPort)
                .setUrl(url)
                .setGatherTime(gatherTime);

            if (MATCH_KEYWORD_YES.equals(split[9])) {
                //获取页面快照
                String snapshot = snapshot(url);
                //进行base64编码
                log.setAttachment(
                    Lists.newArrayList(
                        new Attachment().setTitle("页面快照")
                            .setFile(Base64.encodeBase64String(snapshot.getBytes()))
                    )
                );

            }
            return log;
        }).collect(Collectors.toList());
    }

    /**
     * 获取快照
     *
     * @param url
     * @return
     */
    protected abstract String snapshot(String url);

    /**
     * 过滤操作
     *
     * @param text
     * @return
     */
    protected abstract boolean filter(String text);

    /**
     * 获取idc的所有机房路径
     *
     * @param src
     * @param houseInfos
     * @return
     */
    private List<String> getTargetDir(String src, List<BaseHouseInfo> houseInfos) {
        return houseInfos.stream().map(houseInfo ->
            src + houseInfo.getId()
        ).collect(Collectors.toList());

    }

}
